home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / STRUCTUREDTEXT.PY < prev    next >
Encoding:
Python Source  |  2000-11-10  |  29.5 KB  |  837 lines

  1. #! /usr/bin/env python -- # -*- python -*-
  2. ##############################################################################
  3. # Zope Public License (ZPL) Version 1.0
  4. # -------------------------------------
  5. # Copyright (c) Digital Creations.  All rights reserved.
  6. # This license has been certified as Open Source(tm).
  7. # Redistribution and use in source and binary forms, with or without
  8. # modification, are permitted provided that the following conditions are
  9. # met:
  10. # 1. Redistributions in source code must retain the above copyright
  11. #    notice, this list of conditions, and the following disclaimer.
  12. # 2. Redistributions in binary form must reproduce the above copyright
  13. #    notice, this list of conditions, and the following disclaimer in
  14. #    the documentation and/or other materials provided with the
  15. #    distribution.
  16. # 3. Digital Creations requests that attribution be given to Zope
  17. #    in any manner possible. Zope includes a "Powered by Zope"
  18. #    button that is installed by default. While it is not a license
  19. #    violation to remove this button, it is requested that the
  20. #    attribution remain. A significant investment has been put
  21. #    into Zope, and this effort will continue if the Zope community
  22. #    continues to grow. This is one way to assure that growth.
  23. # 4. All advertising materials and documentation mentioning
  24. #    features derived from or use of this software must display
  25. #    the following acknowledgement:
  26. #      "This product includes software developed by Digital Creations
  27. #      for use in the Z Object Publishing Environment
  28. #      (http://www.zope.org/)."
  29. #    In the event that the product being advertised includes an
  30. #    intact Zope distribution (with copyright and license included)
  31. #    then this clause is waived.
  32. # 5. Names associated with Zope or Digital Creations must not be used to
  33. #    endorse or promote products derived from this software without
  34. #    prior written permission from Digital Creations.
  35. # 6. Modified redistributions of any form whatsoever must retain
  36. #    the following acknowledgment:
  37. #      "This product includes software developed by Digital Creations
  38. #      for use in the Z Object Publishing Environment
  39. #      (http://www.zope.org/)."
  40. #    Intact (re-)distributions of any official Zope release do not
  41. #    require an external acknowledgement.
  42. # 7. Modifications are encouraged but must be packaged separately as
  43. #    patches to official Zope releases.  Distributions that do not
  44. #    clearly separate the patches from the original work must be clearly
  45. #    labeled as unofficial distributions.  Modifications which do not
  46. #    carry the name Zope may be packaged in any form, as long as they
  47. #    conform to all of the clauses above.
  48. # Disclaimer
  49. #   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
  50. #   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  51. #   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  52. #   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
  53. #   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  54. #   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  55. #   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  56. #   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  57. #   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  58. #   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  59. #   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  60. #   SUCH DAMAGE.
  61. # This software consists of contributions made by Digital Creations and
  62. # many individuals on behalf of Digital Creations.  Specific
  63. # attributions are listed in the accompanying credits file.
  64. ##############################################################################
  65. '''Structured Text Manipulation
  66.  
  67. Parse a structured text string into a form that can be used with 
  68. structured formats, like html.
  69.  
  70. Structured text is text that uses indentation and simple
  71. symbology to indicate the structure of a document.  
  72.  
  73. A structured string consists of a sequence of paragraphs separated by
  74. one or more blank lines.  Each paragraph has a level which is defined
  75. as the minimum indentation of the paragraph.  A paragraph is a
  76. sub-paragraph of another paragraph if the other paragraph is the last
  77. preceding paragraph that has a lower level.
  78.  
  79. Special symbology is used to indicate special constructs:
  80.  
  81. - A single-line paragraph whose immediately succeeding paragraphs are lower
  82.   level is treated as a header.
  83.  
  84. - A paragraph that begins with a '-', '*', or 'o' is treated as an
  85.   unordered list (bullet) element.
  86.  
  87. - A paragraph that begins with a sequence of digits followed by a
  88.   white-space character is treated as an ordered list element.
  89.  
  90. - A paragraph that begins with a sequence of sequences, where each
  91.   sequence is a sequence of digits or a sequence of letters followed
  92.   by a period, is treated as an ordered list element.
  93.  
  94. - A paragraph with a first line that contains some text, followed by
  95.   some white-space and '--' is treated as
  96.   a descriptive list element. The leading text is treated as the
  97.   element title.
  98.  
  99. - Sub-paragraphs of a paragraph that ends in the word 'example' or the
  100.   word 'examples', or '::' is treated as example code and is output as is.
  101.  
  102. - Text enclosed single quotes (with white-space to the left of the
  103.   first quote and whitespace or puctuation to the right of the second quote)
  104.   is treated as example code.
  105.  
  106. - Text surrounded by '*' characters (with white-space to the left of the
  107.   first '*' and whitespace or puctuation to the right of the second '*')
  108.   is emphasized.
  109.  
  110. - Text surrounded by '**' characters (with white-space to the left of the
  111.   first '**' and whitespace or puctuation to the right of the second '**')
  112.   is made strong.
  113.  
  114. - Text surrounded by '_' underscore characters (with whitespace to the left 
  115.   and whitespace or punctuation to the right) is made underlined.
  116.  
  117. - Text encloded by double quotes followed by a colon, a URL, and concluded
  118.   by punctuation plus white space, *or* just white space, is treated as a
  119.   hyper link. For example:
  120.  
  121.     "Zope":http://www.zope.org/ is ...
  122.  
  123.   Is interpreted as '<a href="http://www.zope.org/">Zope</a> is ....'
  124.   Note: This works for relative as well as absolute URLs.
  125.  
  126. - Text enclosed by double quotes followed by a comma, one or more spaces,
  127.   an absolute URL and concluded by punctuation plus white space, or just
  128.   white space, is treated as a hyper link. For example: 
  129.  
  130.     "mail me", mailto:amos@digicool.com.
  131.  
  132.   Is interpreted as '<a href="mailto:amos@digicool.com">mail me</a>.' 
  133.  
  134. - Text enclosed in brackets which consists only of letters, digits,
  135.   underscores and dashes is treated as hyper links within the document.
  136.   For example:
  137.     
  138.     As demonstrated by Smith [12] this technique is quite effective.
  139.  
  140.   Is interpreted as '... by Smith <a href="#12">[12]</a> this ...'. Together
  141.   with the next rule this allows easy coding of references or end notes.
  142.  
  143. - Text enclosed in brackets which is preceded by the start of a line, two
  144.   periods and a space is treated as a named link. For example:
  145.  
  146.     .. [12] "Effective Techniques" Smith, Joe ... 
  147.  
  148.   Is interpreted as '<a name="12">[12]</a> "Effective Techniques" ...'.
  149.   Together with the previous rule this allows easy coding of references or
  150.   end notes. 
  151.  
  152.  
  153. - A paragraph that has blocks of text enclosed in '||' is treated as a
  154.   table. The text blocks correspond to table cells and table rows are
  155.   denoted by newlines. By default the cells are center aligned. A cell
  156.   can span more than one column by preceding a block of text with an
  157.   equivalent number of cell separators '||'. Newlines and '|' cannot
  158.   be a part of the cell text. For example:
  159.  
  160.       |||| **Ingredients** ||
  161.       || *Name* || *Amount* ||
  162.       ||Spam||10||
  163.       ||Eggs||3||
  164.  
  165.   is interpreted as::
  166.  
  167.     <TABLE BORDER=1 CELLPADDING=2>
  168.      <TR>
  169.       <TD ALIGN=CENTER COLSPAN=2> <strong>Ingredients</strong> </TD>
  170.      </TR>
  171.      <TR>
  172.       <TD ALIGN=CENTER COLSPAN=1> <em>Name</em> </TD>
  173.       <TD ALIGN=CENTER COLSPAN=1> <em>Amount</em> </TD>
  174.      </TR>
  175.      <TR>
  176.       <TD ALIGN=CENTER COLSPAN=1>Spam</TD>
  177.       <TD ALIGN=CENTER COLSPAN=1>10</TD>
  178.      </TR>
  179.      <TR>
  180.       <TD ALIGN=CENTER COLSPAN=1>Eggs</TD>
  181.       <TD ALIGN=CENTER COLSPAN=1>3</TD>
  182.      </TR>
  183.     </TABLE>
  184.  
  185.     
  186. $Id: StructuredText.py,v 1.27.24.2 2000/11/10 16:55:17 brian Exp $'''
  187. #     Copyright 
  188. #
  189. #       Copyright 1996 Digital Creations, L.C., 910 Princess Anne
  190. #       Street, Suite 300, Fredericksburg, Virginia 22401 U.S.A. All
  191. #       rights reserved.  Copyright in this software is owned by DCLC,
  192. #       unless otherwise indicated. Permission to use, copy and
  193. #       distribute this software is hereby granted, provided that the
  194. #       above copyright notice appear in all copies and that both that
  195. #       copyright notice and this permission notice appear. Note that
  196. #       any product, process or technology described in this software
  197. #       may be the subject of other Intellectual Property rights
  198. #       reserved by Digital Creations, L.C. and are not licensed
  199. #       hereunder.
  200. #
  201. #     Trademarks 
  202. #
  203. #       Digital Creations & DCLC, are trademarks of Digital Creations, L.C..
  204. #       All other trademarks are owned by their respective companies. 
  205. #
  206. #     No Warranty 
  207. #
  208. #       The software is provided "as is" without warranty of any kind,
  209. #       either express or implied, including, but not limited to, the
  210. #       implied warranties of merchantability, fitness for a particular
  211. #       purpose, or non-infringement. This software could include
  212. #       technical inaccuracies or typographical errors. Changes are
  213. #       periodically made to the software; these changes will be
  214. #       incorporated in new editions of the software. DCLC may make
  215. #       improvements and/or changes in this software at any time
  216. #       without notice.
  217. #
  218. #     Limitation Of Liability 
  219. #
  220. #       In no event will DCLC be liable for direct, indirect, special,
  221. #       incidental, economic, cover, or consequential damages arising
  222. #       out of the use of or inability to use this software even if
  223. #       advised of the possibility of such damages. Some states do not
  224. #       allow the exclusion or limitation of implied warranties or
  225. #       limitation of liability for incidental or consequential
  226. #       damages, so the above limitation or exclusion may not apply to
  227. #       you.
  228. #  
  229. #
  230. # If you have questions regarding this software,
  231. # contact:
  232. #
  233. #   Jim Fulton, jim@digicool.com
  234. #
  235. #   (540) 371-6909
  236. #
  237. # $Log: StructuredText.py,v $
  238. # Revision 1.27.24.2  2000/11/10 16:55:17  brian
  239. # Fixed stx to allow ampersand in urls
  240. #
  241. # Revision 1.27.24.1  2000/11/10 16:50:24  brian
  242. # Fixed a typo in STX
  243. #
  244. # Revision 1.27  2000/04/21 13:38:10  jim
  245. # Added closing list tags. Woo hoo!
  246. #
  247. # Revision 1.26  2000/03/14 17:22:04  brian
  248. # Allow ~ in hrefs.
  249. #
  250. # Revision 1.25  2000/02/17 00:53:24  klm
  251. # HTML._str(): We were getting preformatted examples rendered twice,
  252. # second time without preformatting.  Problem was a missing 'continue'
  253. # in one of the cases.
  254. #
  255. # Revision 1.24  1999/12/13 16:32:48  klm
  256. # Incorporated pavlos christoforou's mods to handle simple tables.  From
  257. # his web page at http://www.zope.org/Members/gaaros/StructuredText:
  258. #
  259. #   Structured Text module with table support
  260. #
  261. #   A paragraph that has blocks of text enclosed in '||' is treated as a
  262. #   table. The text blocks correspond to table cells and table rows are
  263. #   denoted by newlines. By default the cells are center aligned. You can
  264. #   change the defaults by modifying the CELL,ROW and TABLE class
  265. #   attributes in class Table. A cell can span more than one column by
  266. #   preceding a block of text with an equivalent number of cell separators
  267. #   '||'. Newlines and '|' cannot be a part of the cell text. If you need
  268. #   newlines use <BR>. For example:
  269. #
  270. #        |||| **Ingredients** ||
  271. #        || *Name* || *Amount* ||
  272. #        ||Spam||10||
  273. #        ||Eggs||3||
  274. #
  275. # Revision 1.23  1999/08/03 20:49:05  jim
  276. # Fixed to allow list elements to introduce examples.
  277. #
  278. # Restructured _str using continue to avoid excessive nesting.
  279. #
  280. # Revision 1.22  1999/08/02 22:01:28  jim
  281. # Fixed a bunch of bugs introduced by making ts_regex actually thread
  282. # safe.
  283. #
  284. # Also localized a bunch of regular expressions
  285. # using "static" variables (aka always default arguments).
  286. #
  287. # Revision 1.21  1999/08/02 13:26:52  jim
  288. # paragraph_divider needs to be a regular (thread-unsafe) regex
  289. # since it gets passed to ts_regex.split, which is thread-safe
  290. # and wants to use regs.
  291. #
  292. # Revision 1.20  1999/07/21 13:33:59  jim
  293. # untabified.
  294. #
  295. # Revision 1.19  1999/07/15 16:43:15  jim
  296. # Checked in Scott Robertson's thread-safety fixes.
  297. #
  298. # Revision 1.18  1999/03/24 00:03:18  klm
  299. # Provide for relative links, eg <a href="file_in_same_dir">whatever</a>,
  300. # as:
  301. #
  302. #   "whatever", :file_in_same_dir
  303. #
  304. # or
  305. #
  306. #   "whatever"::file_in_same_dir
  307. #
  308. # .__init__(): relax the second gsub, using a '*' instead of a '+', so
  309. # the stuff before the ':' can be missing, and also do postprocessing so
  310. # any resulting '<a href=":file_in_same_dir">'s have the superfluous ':'
  311. # removed.  *Seems* good!
  312. #
  313. # Revision 1.17  1999/03/12 23:21:39  klm
  314. # Gratuituous checkin to test my cvs *update* logging hook.
  315. #
  316. # Revision 1.16  1999/03/12 17:12:12  klm
  317. # Added support for underlined elements, in the obvious way (and
  318. # included an entry in the module docstring for it).
  319. #
  320. # Added an entry in the module docstring describing what i *guess* is
  321. # the criterion for identifying header elements.  (I'm going to have to
  322. # delve into and understand the framework a bit better before *knowing*
  323. # this is the case.)
  324. #
  325. # Revision 1.15  1999/03/11 22:40:18  klm
  326. # Handle links that include '#' named links.
  327. #
  328. # Revision 1.14  1999/03/11 01:35:19  klm
  329. # Fixed a small typo, and refined the module docstring link example, in
  330. # order to do a checkin to exercise the CVS repository mirroring.  Might
  331. # as well include my last checkin message, with some substantial stuff:
  332. #
  333. # Links are now recognized whether or not the candidate strings are
  334. # terminated with punctuation before the trailing whitespace.  The old
  335. # form - trailing punctuation then whitespace - is preserved, but the
  336. # punctuation is now unnecessary.
  337. #
  338. # The regular expressions are a bit more complicated, but i've factored
  339. # out the common parts and but them in variables with suggestive names,
  340. # which may make them easier to understand.
  341. #
  342. # Revision 1.13  1999/03/11 00:49:57  klm
  343. # Links are now recognized whether or not the candidate strings are
  344. # terminated with punctuation before the trailing whitespace.  The old
  345. # form - trailing punctuation then whitespace - is preserved, but the
  346. # punctuation is now unnecessary.
  347. #
  348. # The regular expressions are a bit more complicated, but i've factored
  349. # out the common parts and but them in variables with suggestive names,
  350. # which may make them easier to understand.
  351. #
  352. # Revision 1.12  1999/03/10 00:15:46  klm
  353. # Committing with version 1.0 of the license.
  354. #
  355. # Revision 1.11  1999/02/08 18:13:12  klm
  356. # Trival checkin (spelling fix "preceedeing" -> "preceding" and similar)
  357. # to see what pitfalls my environment presents to accomplishing a
  358. # successful checkin.  (It turns out that i can't do it from aldous because
  359. # the new version of cvs doesn't support the '-t' and '-f' options in the
  360. # cvswrappers file...)
  361. #
  362. # Revision 1.10  1998/12/29 22:30:43  amos
  363. # Improved doc string to describe hyper link and references capabilities.
  364. #
  365. # Revision 1.9  1998/12/04 20:15:31  jim
  366. # Detabification and new copyright.
  367. #
  368. # Revision 1.8  1998/02/27 18:45:22  jim
  369. # Various updates, including new indentation utilities.
  370. #
  371. # Revision 1.7  1997/12/12 15:39:54  jim
  372. # Added level as argument for html_with_references.
  373. #
  374. # Revision 1.6  1997/12/12 15:27:25  jim
  375. # Added additional pattern matching for HTML references.
  376. #
  377. # Revision 1.5  1997/03/08 16:01:03  jim
  378. # Moved code to recognize: "foo bar", url.
  379. # into object initializer, so it gets applied in all cases.
  380. #
  381. # Revision 1.4  1997/02/17 23:36:35  jim
  382. # Added support for "foo title", http:/foohost/foo
  383. #
  384. # Revision 1.3  1996/12/06 15:57:37  jim
  385. # Fixed bugs in character tags.
  386. #
  387. # Added -t command-line option to generate title if:
  388. #
  389. #    - The first paragraph is one line (i.e. a heading) and
  390. #
  391. #    - All other paragraphs are indented.
  392. #
  393. # Revision 1.2  1996/10/28 13:56:02  jim
  394. # Fixed bug in ordered lists.
  395. # Added option for either HTML-style headings or descriptive-list style
  396. # headings.
  397. #
  398. # Revision 1.1  1996/10/23 14:00:45  jim
  399. # *** empty log message ***
  400. #
  401. #
  402. #
  403.  
  404. import ts_regex, regex
  405. from ts_regex import gsub
  406. from string import split, join, strip, find
  407.  
  408. def untabify(aString,
  409.              indent_tab=ts_regex.compile('\(\n\|^\)\( *\)\t').search_group,
  410.              ):
  411.     '''\
  412.     Convert indentation tabs to spaces.
  413.     '''
  414.     result=''
  415.     rest=aString
  416.     while 1:
  417.         ts_results = indent_tab(rest, (1,2))
  418.         if ts_results:
  419.             start, grps = ts_results
  420.             lnl=len(grps[0])
  421.             indent=len(grps[1])
  422.             result=result+rest[:start]
  423.             rest="\n%s%s" % (' ' * ((indent/8+1)*8),
  424.                              rest[start+indent+1+lnl:])
  425.         else:
  426.             return result+rest
  427.  
  428. def indent(aString, indent=2):
  429.     """Indent a string the given number of spaces"""
  430.     r=split(untabify(aString),'\n')
  431.     if not r: return ''
  432.     if not r[-1]: del r[-1]
  433.     tab=' '*level
  434.     return "%s%s\n" % (tab,join(r,'\n'+tab))
  435.  
  436. def reindent(aString, indent=2, already_untabified=0):
  437.     "reindent a block of text, so that the minimum indent is as given"
  438.  
  439.     if not already_untabified: aString=untabify(aString)
  440.  
  441.     l=indent_level(aString)[0]
  442.     if indent==l: return aString
  443.  
  444.     r=[]
  445.  
  446.     append=r.append
  447.  
  448.     if indent > l:
  449.         tab=' ' * (indent-l)
  450.         for s in split(aString,'\n'): append(tab+s)
  451.     else:
  452.         l=l-indent
  453.         for s in split(aString,'\n'): append(s[l:])
  454.  
  455.     return join(r,'\n')
  456.  
  457. def indent_level(aString,
  458.                  indent_space=ts_regex.compile('\n\( *\)').search_group,
  459.                  ):
  460.     '''\
  461.     Find the minimum indentation for a string, not counting blank lines.
  462.     '''
  463.     start=0
  464.     text='\n'+aString
  465.     indent=l=len(text)
  466.     while 1:
  467.  
  468.         ts_results = indent_space(text, (1,2), start)
  469.         if ts_results:
  470.             start, grps = ts_results
  471.             i=len(grps[0])
  472.             start=start+i+1
  473.             if start < l and text[start] != '\n':       # Skip blank lines
  474.                 if not i: return (0,aString)
  475.                 if i < indent: indent = i
  476.         else:
  477.             return (indent,aString)
  478.  
  479. def paragraphs(list,start):
  480.     l=len(list)
  481.     level=list[start][0]
  482.     i=start+1
  483.     while i < l and list[i][0] > level: i=i+1
  484.     return i-1-start
  485.  
  486. def structure(list):
  487.     if not list: return []
  488.     i=0
  489.     l=len(list)
  490.     r=[]
  491.     while i < l:
  492.         sublen=paragraphs(list,i)
  493.         i=i+1
  494.         r.append((list[i-1][1],structure(list[i:i+sublen])))
  495.         i=i+sublen
  496.     return r
  497.  
  498.  
  499. class Table:
  500.     CELL='  <TD ALIGN=CENTER COLSPAN=%i>%s</TD>\n'
  501.     ROW=' <TR>\n%s </TR>\n'
  502.     TABLE='\n<TABLE BORDER=1 CELLPADDING=2>\n%s</TABLE>'
  503.     
  504.     def create(self,aPar,td=ts_regex.compile(
  505.         '[ \t\n]*||\([^\0|]*\)').match_group):
  506.         '''parses a table and returns nested list representing the
  507.         table'''
  508.         self.table=[]
  509.         text=filter(None,split(aPar,'\n'))
  510.         for line in text:
  511.             row=[]
  512.             while 1:
  513.                 pos=td(line,(1,))
  514.                 if not pos:return 0
  515.                 row.append(pos[1])
  516.                 if pos[0]==len(line):break
  517.                 line=line[pos[0]:]
  518.             self.table.append(row)
  519.         return 1
  520.  
  521.     def html(self):
  522.         '''Creates an HTML representation of table'''
  523.         htmltable=[]
  524.         for row in self.table:
  525.             htmlrow=[]
  526.             colspan=1
  527.             for cell in row:
  528.                 if cell=='':
  529.                     colspan=colspan+1
  530.                     continue
  531.                 else:
  532.                     htmlrow.append(self.CELL%(colspan,cell))
  533.                     colspan=1
  534.             htmltable.append(self.ROW%join(htmlrow,''))
  535.         return self.TABLE%join(htmltable,'')
  536.  
  537. optional_trailing_punctuation = '\(,\|\([.:?;]\)\)?'
  538. trailing_space = '\([\0- ]\)'
  539. not_punctuation_or_whitespace = "[^-,.?:\0- ]"
  540. table=Table()
  541.  
  542. class StructuredText:
  543.  
  544.     """Model text as structured collection of paragraphs.
  545.  
  546.     Structure is implied by the indentation level.
  547.  
  548.     This class is intended as a base classes that do actual text
  549.     output formatting.
  550.     """
  551.  
  552.     def __init__(self, aStructuredString, level=0,
  553.                  paragraph_divider=regex.compile('\(\n *\)+\n'),
  554.                  ):
  555.         '''Convert a structured text string into a structured text object.
  556.  
  557.         Aguments:
  558.  
  559.           aStructuredString -- The string to be parsed.
  560.           level -- The level of top level headings to be created.
  561.         '''
  562.  
  563.         aStructuredString = gsub(
  564.             '\"\([^\"\0]+\)\":'         # title: <"text":>
  565.             + ('\([-:a-zA-Z0-9_,./?=@#~&]+%s\)'
  566.                % not_punctuation_or_whitespace)
  567.             + optional_trailing_punctuation
  568.             + trailing_space,
  569.             '<a href="\\2">\\1</a>\\4\\5\\6',
  570.             aStructuredString)
  571.  
  572.         aStructuredString = gsub(
  573.             '\"\([^\"\0]+\)\",[\0- ]+'            # title: <"text", >
  574.             + ('\([a-zA-Z]*:[-:a-zA-Z0-9_,./?=@#~&]*%s\)'
  575.                % not_punctuation_or_whitespace)
  576.             + optional_trailing_punctuation
  577.             + trailing_space,
  578.             '<a href="\\2">\\1</a>\\4\\5\\6',
  579.             aStructuredString)
  580.  
  581.         protoless = find(aStructuredString, '<a href=":')
  582.         if protoless != -1:
  583.             aStructuredString = gsub('<a href=":', '<a href="',
  584.                                      aStructuredString)
  585.  
  586.         self.level=level
  587.         paragraphs=ts_regex.split(untabify(aStructuredString),
  588.                                   paragraph_divider)
  589.         paragraphs=map(indent_level,paragraphs)
  590.  
  591.         self.structure=structure(paragraphs)
  592.  
  593.  
  594.     def __str__(self):
  595.         return str(self.structure)
  596.  
  597.  
  598. ctag_prefix="\([\0- (]\|^\)"
  599. ctag_suffix="\([\0- ,.:;!?)]\|$\)"
  600. ctag_middle="[%s]\([^\0- %s][^%s]*[^\0- %s]\|[^%s]\)[%s]"
  601. ctag_middl2="[%s][%s]\([^\0- %s][^%s]*[^\0- %s]\|[^%s]\)[%s][%s]"
  602.         
  603. def ctag(s,
  604.          em=regex.compile(
  605.              ctag_prefix+(ctag_middle % (("*",)*6) )+ctag_suffix),
  606.          strong=regex.compile(
  607.              ctag_prefix+(ctag_middl2 % (("*",)*8))+ctag_suffix),
  608.          under=regex.compile(
  609.              ctag_prefix+(ctag_middle % (("_",)*6) )+ctag_suffix),
  610.          code=regex.compile(
  611.              ctag_prefix+(ctag_middle % (("\'",)*6))+ctag_suffix),
  612.          ):
  613.     if s is None: s=''
  614.     s=gsub(strong,'\\1<strong>\\2</strong>\\3',s)
  615.     s=gsub(under, '\\1<u>\\2</u>\\3',s)
  616.     s=gsub(code,  '\\1<code>\\2</code>\\3',s)
  617.     s=gsub(em,    '\\1<em>\\2</em>\\3',s)
  618.     return s    
  619.  
  620. class HTML(StructuredText):
  621.  
  622.     '''\
  623.     An HTML structured text formatter.
  624.     '''\
  625.  
  626.     def __str__(self,
  627.                 extra_dl=regex.compile("</dl>\n<dl>"),
  628.                 extra_ul=regex.compile("</ul>\n<ul>"),
  629.                 extra_ol=regex.compile("</ol>\n<ol>"),
  630.                 ):
  631.         '''\
  632.         Return an HTML string representation of the structured text data.
  633.  
  634.         '''
  635.         s=self._str(self.structure,self.level)
  636.         s=gsub(extra_dl,'\n',s)
  637.         s=gsub(extra_ul,'\n',s)
  638.         s=gsub(extra_ol,'\n',s)
  639.         return s
  640.  
  641.     def ul(self, before, p, after):
  642.         if p: p="<p>%s</p>" % strip(ctag(p))
  643.         return ('%s<ul><li>%s\n%s\n</li></ul>\n'
  644.                 % (before,p,after))
  645.  
  646.     def ol(self, before, p, after):
  647.         if p: p="<p>%s</p>" % strip(ctag(p))
  648.         return ('%s<ol><li>%s\n%s\n</li></ol>\n'
  649.                 % (before,p,after))
  650.  
  651.     def dl(self, before, t, d, after):
  652.         return ('%s<dl><dt>%s</dt><dd><p>%s</p>\n%s\n</dd></dl>\n'
  653.                 % (before,ctag(t),ctag(d),after))
  654.  
  655.     def head(self, before, t, level, d):
  656.         if level > 0 and level < 6:
  657.             return ('%s<h%d>%s</h%d>\n%s\n'
  658.                     % (before,level,strip(ctag(t)),level,d))
  659.             
  660.         t="<p><strong>%s</strong></p>" % strip(ctag(t))
  661.         return ('%s<dl><dt>%s\n</dt><dd>%s\n</dd></dl>\n'
  662.                 % (before,t,d))
  663.  
  664.     def normal(self,before,p,after):
  665.         return '%s<p>%s</p>\n%s\n' % (before,ctag(p),after)
  666.  
  667.     def pre(self,structure,tagged=0):
  668.         if not structure: return ''
  669.         if tagged:
  670.             r=''
  671.         else:
  672.             r='<PRE>\n'
  673.         for s in structure:
  674.             r="%s%s\n\n%s" % (r,html_quote(s[0]),self.pre(s[1],1))
  675.         if not tagged: r=r+'</PRE>\n'
  676.         return r
  677.     
  678.     def table(self,before,table,after):
  679.         return '%s<p>%s</p>\n%s\n' % (before,ctag(table),after)
  680.     
  681.     def _str(self,structure,level,
  682.              # Static
  683.              bullet=ts_regex.compile('[ \t\n]*[o*-][ \t\n]+\([^\0]*\)'
  684.                                      ).match_group,
  685.              example=ts_regex.compile('[\0- ]examples?:[\0- ]*$'
  686.                                       ).search,
  687.              dl=ts_regex.compile('\([^\n]+\)[ \t]+--[ \t\n]+\([^\0]*\)'
  688.                                  ).match_group,
  689.              nl=ts_regex.compile('\n').search,
  690.              ol=ts_regex.compile(
  691.                  '[ \t]*\(\([0-9]+\|[a-zA-Z]+\)[.)]\)+[ \t\n]+\([^\0]*\|$\)'
  692.                  ).match_group,
  693.              olp=ts_regex.compile('[ \t]*([0-9]+)[ \t\n]+\([^\0]*\|$\)'
  694.                                   ).match_group,
  695.              ):
  696.         r=''
  697.         for s in structure:
  698.  
  699.             ts_results = bullet(s[0], (1,))
  700.             if ts_results:
  701.                 p = ts_results[1]
  702.                 if s[0][-2:]=='::' and s[1]: ps=self.pre(s[1])
  703.                 else: ps=self._str(s[1],level)
  704.                 r=self.ul(r,p,ps)
  705.                 continue
  706.             ts_results = ol(s[0], (3,))
  707.             if ts_results:
  708.                 p = ts_results[1]
  709.                 if s[0][-2:]=='::' and s[1]: ps=self.pre(s[1])
  710.                 else: ps=self._str(s[1],level)
  711.                 r=self.ol(r,p,ps)
  712.                 continue
  713.             ts_results = olp(s[0], (1,))
  714.             if ts_results:
  715.                 p = ts_results[1]
  716.                 if s[0][-2:]=='::' and s[1]: ps=self.pre(s[1])
  717.                 else: ps=self._str(s[1],level)
  718.                 r=self.ol(r,p,ps)
  719.                 continue
  720.             ts_results = dl(s[0], (1,2))
  721.             if ts_results:
  722.                 t,d = ts_results[1]
  723.                 r=self.dl(r,t,d,self._str(s[1],level))
  724.                 continue
  725.             if example(s[0]) >= 0 and s[1]:
  726.                 # Introduce an example, using pre tags:
  727.                 r=self.normal(r,s[0],self.pre(s[1]))
  728.                 continue
  729.             if s[0][-2:]=='::' and s[1]:
  730.                 # Introduce an example, using pre tags:
  731.                 r=self.normal(r,s[0][:-1],self.pre(s[1]))
  732.                 continue
  733.             if table.create(s[0]):
  734.                 ## table support.
  735.                 r=self.table(r,table.html(),self._str(s[1],level))
  736.                 continue
  737.             else:
  738.  
  739.                 if nl(s[0]) < 0 and s[1] and s[0][-1:] != ':':
  740.                     # Treat as a heading
  741.                     t=s[0]
  742.                     r=self.head(r,t,level,
  743.                                 self._str(s[1],level and level+1))
  744.                 else:
  745.                     r=self.normal(r,s[0],self._str(s[1],level))
  746.         return r
  747.         
  748.  
  749. def html_quote(v,
  750.                character_entities=(
  751.                        (regex.compile('&'), '&'),
  752.                        (regex.compile("<"), '<' ),
  753.                        (regex.compile(">"), '>' ),
  754.                        (regex.compile('"'), '"')
  755.                        )): #"
  756.         text=str(v)
  757.         for re,name in character_entities:
  758.             text=gsub(re,name,text)
  759.         return text
  760.  
  761. def html_with_references(text, level=1):
  762.     text = gsub(
  763.         '[\0\n].. \[\([-_0-9_a-zA-Z-]+\)\]',
  764.         '\n  <a name="\\1">[\\1]</a>',
  765.         text)
  766.     
  767.     text = gsub(
  768.         '\([\0- ,]\)\[\([0-9_a-zA-Z-]+\)\]\([\0- ,.:]\)',
  769.         '\\1<a href="#\\2">[\\2]</a>\\3',
  770.         text)
  771.     
  772.     text = gsub(
  773.         '\([\0- ,]\)\[\([^]]+\)\.html\]\([\0- ,.:]\)',
  774.         '\\1<a href="\\2.html">[\\2]</a>\\3',
  775.         text)
  776.  
  777.     return HTML(text,level=level)
  778.     
  779.  
  780. def main():
  781.     import sys, getopt
  782.  
  783.     opts,args=getopt.getopt(sys.argv[1:],'tw')
  784.  
  785.     if args:
  786.         [infile]=args
  787.         s=open(infile,'r').read()
  788.     else:
  789.         s=sys.stdin.read()
  790.  
  791.     if opts:
  792.  
  793.         if filter(lambda o: o[0]=='-w', opts):
  794.             print 'Content-Type: text/html\n'
  795.  
  796.         if s[:2]=='#!':
  797.             s=ts_regex.sub('^#![^\n]+','',s)
  798.  
  799.         r=ts_regex.compile('\([\0-\n]*\n\)')
  800.         ts_results = r.match_group(s, (1,))
  801.         if ts_results:
  802.             s=s[len(ts_results[1]):]
  803.         s=str(html_with_references(s))
  804.         if s[:4]=='<h1>':
  805.             t=s[4:find(s,'</h1>')]
  806.             s='''<html><head><title>%s</title>
  807.             </head><body>
  808.             %s
  809.             </body></html>
  810.             ''' % (t,s)
  811.         print s
  812.     else:
  813.         print html_with_references(s)
  814.  
  815. if __name__=="__main__": main()
  816.